/*
 * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.eventbus;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

class SubscriberMethodFinder
{
	/*
	 * In newer class files, compilers may add methods. Those are called bridge or synthetic methods.
	 * EventBus must ignore both. There modifiers are not public but defined in the Java class file format:
	 * http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1
	 */
	private static final int BRIDGE = 0x40;
	private static final int SYNTHETIC = 0x1000;

	private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
	private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

	private List<SubscriberInfoIndex> subscriberInfoIndexes;
	private final boolean strictMethodVerification;
	private final boolean ignoreGeneratedIndex;

	private static final int POOL_SIZE = 4;
	private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

	SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification, boolean ignoreGeneratedIndex)
	{
		this.subscriberInfoIndexes = subscriberInfoIndexes;
		this.strictMethodVerification = strictMethodVerification;
		this.ignoreGeneratedIndex = ignoreGeneratedIndex;
	}

	/**
	 * 获取订阅者的有效注解方法
	 * @param subscriberClass subscriberClass
	 * @return 有效注解方法list
	 */
	List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)
	{
		List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
		if (subscriberMethods != null)
		{
			return subscriberMethods;
		}

		if (ignoreGeneratedIndex)
		{
			subscriberMethods = findUsingReflection(subscriberClass);
		} else
		{
			subscriberMethods = findUsingInfo(subscriberClass);
		}
		if (subscriberMethods.isEmpty())
		{
			//我修改 无对应方法抛异常----已取消掉（可以只注册，不监听）
//			throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation");
			return subscriberMethods;
		} else
		{
			METHOD_CACHE.put(subscriberClass, subscriberMethods);
			return subscriberMethods;
		}
	}

	/**
	 * 通过引入apt 获取订阅者的方法，速度是反射的5-6倍
	 * @param subscriberClass subscriberClass
	 * @return 有效注解方法list
	 */
	private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass)
	{
		FindState findState = prepareFindState();
		findState.initForSubscriber(subscriberClass);
		while (findState.clazz != null)
		{
			findState.subscriberInfo = getSubscriberInfo(findState);
			if (findState.subscriberInfo != null)
			{
				SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
				for (SubscriberMethod subscriberMethod : array)
				{
					if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType))
					{
						findState.subscriberMethods.add(subscriberMethod);
					}
				}
			} else
			{
				findUsingReflectionInSingleClass(findState);
			}
			findState.moveToSuperclass();
		}
		return getMethodsAndRelease(findState);
	}

	private List<SubscriberMethod> getMethodsAndRelease(FindState findState)
	{
		List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
		findState.recycle();
		synchronized (FIND_STATE_POOL)
		{
			for (int i = 0; i < POOL_SIZE; i++)
			{
				if (FIND_STATE_POOL[i] == null)
				{
					FIND_STATE_POOL[i] = findState;
					break;
				}
			}
		}
		return subscriberMethods;
	}

	private FindState prepareFindState()
	{
		synchronized (FIND_STATE_POOL)
		{
			for (int i = 0; i < POOL_SIZE; i++)
			{
				FindState state = FIND_STATE_POOL[i];
				if (state != null)
				{
					FIND_STATE_POOL[i] = null;
					return state;
				}
			}
		}
		return new FindState();
	}

	private SubscriberInfo getSubscriberInfo(FindState findState)
	{
		if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null)
		{
			SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
			if (findState.clazz == superclassInfo.getSubscriberClass())
			{
				return superclassInfo;
			}
		}
		if (subscriberInfoIndexes != null)
		{
			for (SubscriberInfoIndex index : subscriberInfoIndexes)
			{
				SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
				if (info != null)
				{
					return info;
				}
			}
		}
		return null;
	}

	/**
	 * 通过反射获取订阅者的方法
	 * @param subscriberClass subscriberClass
	 * @return 有效注解方法list
	 */
	private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass)
	{
		FindState findState = prepareFindState();
		findState.initForSubscriber(subscriberClass);
		while (findState.clazz != null)
		{
			findUsingReflectionInSingleClass(findState);
			findState.moveToSuperclass();
		}
		return getMethodsAndRelease(findState);
	}

	private void findUsingReflectionInSingleClass(FindState findState)
	{
		Method[] methods;
		try
		{
			// This is faster than getMethods, especially when subscribers are fat classes like Activities
			methods = findState.clazz.getDeclaredMethods();
		} catch (Throwable th)
		{
			// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
			methods = findState.clazz.getMethods();
			findState.skipSuperClasses = true;
		}
		for (Method method : methods)
		{
			int modifiers = method.getModifiers();
			if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0)
			{
				Class<?>[] parameterTypes = method.getParameterTypes();
				if (parameterTypes.length >= 1)
				{//我修改
					Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
					if (subscribeAnnotation != null)
					{
						Class<?> eventType = parameterTypes[0];
						if (findState.checkAdd(method, eventType))
						{
							findState.subscriberMethods.add(new SubscriberMethod(method, eventType, subscribeAnnotation.threadMode(),
									subscribeAnnotation.priority(), subscribeAnnotation.sticky(), subscribeAnnotation.tag()));//我修改
						}
					}
				} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class))
				{
					String methodName = method.getDeclaringClass().getName() + "." + method.getName();
					throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length);
				}
			} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class))
			{
				String methodName = method.getDeclaringClass().getName() + "." + method.getName();
				throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
			}
		}
	}

	static void clearCaches()
	{
		METHOD_CACHE.clear();
	}

	private static class FindState
	{
		final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
		final Map<Class, Object> anyMethodByEventType = new HashMap<>();
		final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
		final StringBuilder methodKeyBuilder = new StringBuilder(128);

		Class<?> subscriberClass;
		Class<?> clazz;
		boolean skipSuperClasses;
		SubscriberInfo subscriberInfo;

		void initForSubscriber(Class<?> subscriberClass)
		{
			this.subscriberClass = clazz = subscriberClass;
			skipSuperClasses = false;
			subscriberInfo = null;
		}

		void recycle()
		{
			subscriberMethods.clear();
			anyMethodByEventType.clear();
			subscriberClassByMethodKey.clear();
			methodKeyBuilder.setLength(0);
			subscriberClass = null;
			clazz = null;
			skipSuperClasses = false;
			subscriberInfo = null;
		}

		boolean checkAdd(Method method, Class<?> eventType)
		{
			// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
			// Usually a subscriber doesn't have methods listening to the same event type.
			Object existing = anyMethodByEventType.put(eventType, method);
			if (existing == null)
			{
				return true;
			} else
			{
				if (existing instanceof Method)
				{
					if (!checkAddWithMethodSignature((Method) existing, eventType))
					{
						// Paranoia check
						throw new IllegalStateException();
					}
					// Put any non-Method object to "consume" the existing Method
					anyMethodByEventType.put(eventType, this);
				}
				return checkAddWithMethodSignature(method, eventType);
			}
		}

		private boolean checkAddWithMethodSignature(Method method, Class<?> eventType)
		{
			methodKeyBuilder.setLength(0);
			methodKeyBuilder.append(method.getName());
			methodKeyBuilder.append('>').append(eventType.getName());

			String methodKey = methodKeyBuilder.toString();
			Class<?> methodClass = method.getDeclaringClass();
			Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
			if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass))
			{
				// Only add if not already found in a sub class
				return true;
			} else
			{
				// Revert the put, old class is further down the class hierarchy
				subscriberClassByMethodKey.put(methodKey, methodClassOld);
				return false;
			}
		}

		void moveToSuperclass()
		{
			if (skipSuperClasses)
			{
				clazz = null;
			} else
			{
				clazz = clazz.getSuperclass();
				String clazzName = clazz.getName();
				/** Skip system classes, this just degrades performance. */
				if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android."))
				{
					clazz = null;
				}
			}
		}
	}

}
